import * as THREE from 'three'
import { LineMaterial, LineSegments2, LineSegmentsGeometry } from 'three/addons'

export function LineUtils (scene, renderer, camera) {
  let lineSegments = null
  let linesData = []
  let LineList = []
  let highlightedIndices = new Set()
  const raycaster = new THREE.Raycaster()
  const mouse = new THREE.Vector2()
  let arrowHelpers = []

  function dispose () {
    if (lineSegments) {
      scene.remove(lineSegments)
      lineSegments.geometry.dispose()
      lineSegments.material.dispose()
      lineSegments = null
    }
    arrowHelpers.forEach(arrow => scene.remove(arrow))
    arrowHelpers = []
    linesData = []
    highlightedIndices.clear()
  }

  function updateResolution () {
    if (lineSegments) {
      const mat = lineSegments.material
      mat.resolution.set(
        renderer.domElement.clientWidth,
        renderer.domElement.clientHeight
      )
    }
  }

  function drawLineSegments (lines, { linewidth = 3, transparent = true, opacity = 1, addArrow = false } = {}) {
    dispose()
    linesData = lines.map((line, i) => ({ ...line, __index: i }))
    // const list = []
    const positions = []
    const colors = []

    linesData.forEach(({ start, end, color = 0x00ffff }) => {
      positions.push(start.x, start.y, start.z)
      positions.push(end.x, end.y, end.z)

      const c = new THREE.Color(color)
      colors.push(c.r, c.g, c.b, c.r, c.g, c.b)

      if (addArrow) {
        const dir = new THREE.Vector3().subVectors(end, start).normalize()
        const arrow = new THREE.ArrowHelper(dir, end.clone(), 6, c.getHex(), 6, 3)
        arrowHelpers.push(arrow)
        scene.add(arrow)
      }
    })

    const geometry = new LineSegmentsGeometry()
    geometry.setPositions(positions)
    geometry.setColors(colors)
    const material = new LineMaterial({
      linewidth,
      transparent,
      opacity,
      vertexColors: true,
      color: 0xffffff,
      resolution: new THREE.Vector2(renderer.domElement.clientWidth, renderer.domElement.clientHeight)
    })
    lineSegments = new LineSegments2(geometry, material)
    scene.add(lineSegments)
  }

  function setLineList () {
    return LineList
  }

  function generateBezierLines ({
    start,
    control1,
    control2,
    end,
    colorFrom = 0x00ffff,
    colorTo = null,
    segments,
    key = 0
  }) {
    const curve = new THREE.CubicBezierCurve3(start, control1, control2, end)
    const estimatedLength = curve.getLength()
    const segCount = segments ?? Math.ceil(estimatedLength / 2)
    const points = curve.getPoints(segCount)
    const lines = []

    for (let i = 0; i < points.length - 1; i++) {
      const p1 = points[i]
      const p2 = points[i + 1]

      let color = colorFrom
      if (colorTo) {
        const c1 = new THREE.Color(colorFrom)
        const c2 = new THREE.Color(colorTo)
        color = c1.clone().lerp(c2, i / (points.length - 1))
      }

      lines.push({ start: p1, end: p2, color, id: `Bline${key}-${i}` })
    }

    return lines
  }

  function handleClick (event, domElement) {
    if (!lineSegments) return

    const rect = domElement.getBoundingClientRect()
    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

    raycaster.setFromCamera(mouse, camera)
    const intersects = raycaster.intersectObject(lineSegments)

    if (intersects.length > 0) {
      const instanceIndex = intersects[0].instanceId
      highlightLines([instanceIndex])
    }
  }

  function highlightLines (idsOrIndexes) {
    if (!lineSegments) return
    const startColor = lineSegments.geometry.getAttribute('instanceColorStart')
    const endColor = lineSegments.geometry.getAttribute('instanceColorEnd')
    if (!startColor || !endColor) return
    const highlightColor = new THREE.Color(0xff00ff) // 洋紫红
    const toHighlight = new Set()

    idsOrIndexes.forEach(entry => {
      const index = typeof entry === 'number' ? entry : getIndexById(entry)
      if (index !== -1) {
        startColor.setXYZ(index, highlightColor.r, highlightColor.g, highlightColor.b)
        endColor.setXYZ(index, highlightColor.r, highlightColor.g, highlightColor.b)
        toHighlight.add(index)
      }
    })
    endColor.needsUpdate = true
    startColor.needsUpdate = true
    highlightedIndices = toHighlight
  }

  function clearHighlights () {
    if (!lineSegments) return
    const startColor = lineSegments.geometry.getAttribute('instanceColorStart')
    const endColor = lineSegments.geometry.getAttribute('instanceColorEnd')
    if (!startColor || !endColor) return
    highlightedIndices.forEach(index => {
      const line = linesData[index]
      const c = new THREE.Color(line.color || 0x00ffff)
      startColor.setXYZ(index, c.r, c.g, c.b)
      endColor.setXYZ(index, c.r, c.g, c.b)
    })

    endColor.needsUpdate = true
    startColor.needsUpdate = true
    highlightedIndices.clear()
  }

  function getIndexById (id) {
    const found = linesData.find(line => line.id === id)
    return found ? found.__index : -1
  }

  function highlightBy (attr, value) {
    const match = linesData.filter(line => line[attr] === value).map(l => l.__index)
    highlightLines(match)
  }

  return {
    drawLineSegments,
    generateBezierLines,
    updateResolution,
    dispose,
    handleClick,
    highlightLines,
    clearHighlights,
    highlightBy,
    setLineList
  }
}
